package com.zqh.myfmmall.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zqh.myfmmall.constants.RedisExpireTime;
import com.zqh.myfmmall.dao.ProductDao;
import com.zqh.myfmmall.doc.ProductDocument;
import com.zqh.myfmmall.entity.Product;
import com.zqh.myfmmall.entity.ProductImg;
import com.zqh.myfmmall.redis.RedisUtil;
import com.zqh.myfmmall.result.Result;
import com.zqh.myfmmall.service.ProductImgService;
import com.zqh.myfmmall.service.ProductService;
import com.zqh.myfmmall.vo.ProductVo;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 获取最新上架商品
 *
 * @author KONO 曾淇杭哒！
 * @since 2022-01-20 20:53:00
 */
@Service("productService")
public class ProductServiceImpl extends ServiceImpl<ProductDao, Product> implements ProductService {
    @Resource
    private ProductImgService productImgService;
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private RestHighLevelClient restHighLevelClient;

    @Override
    public Result latestProducts() throws JsonProcessingException {
        List<Product> latestProduct = (List<Product>) redisUtil.get("latestProduct");
        Result result = null;
        //categories-list一级缓存不为空
        if (latestProduct != null) {
            result = Result.success(latestProduct);
            //二级缓存配合redis分布式锁解决缓存击穿问题
        }else{
            List<Product> latestProductBackUp = (List<Product>) redisUtil.get("latestProduct-backup");
            if (latestProductBackUp != null) {
                //使用缓存备份作为数据返回，在后面更新缓存数据的时间内的请求会有短时数据不同步问题
                result = Result.success(latestProductBackUp);
                //添加更新categories缓存的锁，从而只启动一个线程来更新缓存
                boolean isLocked = redisUtil.setnx("latestProduct-lock", "1");
                //加锁成功则进入进行缓存更新操作
                if (isLocked) {
                    QueryWrapper<Product> productQueryWrapper = new QueryWrapper<>();
                    //获取最新上架的十个商品
                    Page<Product> page = new Page<>(1, 10);
                    ArrayList<ProductVo> productVos = new ArrayList<>();
                    productQueryWrapper.orderByDesc("create_time");
                    page(page, productQueryWrapper);
                    List<Product> records = page.getRecords();
                    for (Product record : records) {
                        List<ProductImg> productImgs = productImgService.selectProductImgByProductId(record.getProductId());
                        ProductVo productVo = new ProductVo(record);
                        productVo.setProductImgs(productImgs);
                        productVos.add(productVo);
                    }
                    //更新缓存
                    redisUtil.set("latestProduct",records,
                            RedisExpireTime.PRODUCT.getTime(),RedisExpireTime.PRODUCT.getTimeUnit());
                    redisUtil.set("latestProduct-backup",records,
                            RedisExpireTime.PRODUCT.getTime(),RedisExpireTime.PRODUCT.getTimeUnit());
                    //释放锁
                    redisUtil.del("latestProduct-lock");
                }
                //初始categories-list和categories-list-backup缓存数据(即第一次访问时进行)，但是这里可以通过缓存预热来优化，暂时先这样写
            }else {
                QueryWrapper<Product> productQueryWrapper = new QueryWrapper<>();
                //获取最新上架的十个商品
                Page<Product> page = new Page<>(1, 10);
                ArrayList<ProductVo> productVos = new ArrayList<>();
                productQueryWrapper.orderByDesc("create_time");
                page(page, productQueryWrapper);
                List<Product> records = page.getRecords();
                for (Product record : records) {
                    List<ProductImg> productImgs = productImgService.selectProductImgByProductId(record.getProductId());
                    ProductVo productVo = new ProductVo(record);
                    productVo.setProductImgs(productImgs);
                    productVos.add(productVo);
                }
                redisUtil.set("latestProduct",records,
                        RedisExpireTime.PRODUCT.getTime(),RedisExpireTime.PRODUCT.getTimeUnit());
                redisUtil.set("latestProduct-backup",records,
                        RedisExpireTime.PRODUCT.getTime(),RedisExpireTime.PRODUCT.getTimeUnit());
                result = Result.success(records);
            }

        }
        return result;

    }

    //还没解决缓存击穿问题，按照上面那个方法改下就好
    @Override
    public List<ProductVo> firstLevelCategoriesHighestSaleProduct(String firstLevelCategoryId) {
        QueryWrapper<Product> productQueryWrapper = new QueryWrapper<>();
        ArrayList<ProductVo> productVos = new ArrayList<>();
        Page<Product> productVoPage = new Page<>(1,6);
        productQueryWrapper.eq("root_category_id",firstLevelCategoryId).orderByDesc("sold_num");
        page(productVoPage,productQueryWrapper);
        List<Product> records = productVoPage.getRecords();
        for (Product record : records) {
            List<ProductImg> productImgs = productImgService.selectProductImgByProductId(record.getProductId());
            ProductVo productVo = new ProductVo(record);
            productVo.setProductImgs(productImgs);
            productVos.add(productVo);
        }
        return productVos;
    }

    @Override
    public Result search(String key, Integer pageNum, Integer pageSize) throws Exception {
        SearchRequest searchRequest = new SearchRequest("myfmmall_product");
        ObjectMapper objectMapper = new ObjectMapper();
        Page<ProductDocument> page = new Page<>();
        ArrayList<ProductDocument> productDocuments = new ArrayList<>();
        //因为在创建索引时忘记使用copytoall构造all字段，所以这里就用productName来进行查询，最好是通过all+match查询来，效率高
        //在拆分为微服务时索引已修改添加all字段
        searchRequest.source().query(QueryBuilders.matchQuery("productName",key));
        searchRequest.source().from((pageNum - 1) * pageSize).size(pageSize);
        searchRequest.source().sort("soldNum", SortOrder.DESC).sort("productSkuLowestPrice",SortOrder.ASC);
        searchRequest.source().highlighter(new HighlightBuilder().field("productName").requireFieldMatch(false));
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        //结果解析
        SearchHits searchHits = searchResponse.getHits();
        page.setTotal(searchHits.getTotalHits().value);
        SearchHit[] hits = searchHits.getHits();
        for (SearchHit hit : hits) {
            ProductDocument productDocument = objectMapper.readValue(hit.getSourceAsString(), ProductDocument.class);
            productDocuments.add(productDocument);
            //解析高亮数据
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            HighlightField highlightField = highlightFields.get("productName");
            if (highlightField != null) {
                String highlightProductName = highlightField.getFragments()[0].string();
                productDocument.setProductName(highlightProductName);
            }
        }
        page.setRecords(productDocuments);
        return Result.success(page);
    }
}

